



// 通过 http-proxy-middleware 开启反向代理，实现跨域
const { createProxyMiddleware } = require("http-proxy-middleware");

/**
 * gulp-load-plugins 按需加载插件（惰性加载）
 * 自动加载 package.json 文件里的 gulp 插件。
 * 使用插件时，将原始插件名去掉 gulp- 前缀，再转换为驼峰命名即可。如 gulp-clean-css 使用 $.cleanCss 来代替。
 */
// A - 从多个json文件搜索插件

// // 使用lodash.merge将多个文件合并为一个对象，之所以要合并，是因为load-plugins的配置项 config只接受一个值；
// const merge = require("lodash.merge");

// // 使用合并后的文件
// const $ = gulpLoadPlugins({
//     config: merge(
// require("dev/package.json"),
// require("./package.json")
// )
// })
// 扩展配置器列表

// const overridePlugins = require('gulp-load-plugins')({
//   // 默认情况下：覆盖内置匹配项的配置为true
//   overridePattern: true,
//   pattern: ['gulp-*', 'gulp.*', '@*/gulp{-,.}*', 'foo-bar']
// });
// // 设置复写内置匹配项为false，则会在内置匹配项上扩展新增匹配项
// const extendedPlugins = require('gulp-load-plugins')({
//   overridePattern: false,
//   pattern: ['foo-bar']
// });

// 删除注释
const comment = require("gulp-strip-comments");

//gulp-streamify 只支持 buffer 的插件直接处理 stream



const lazypipe = require("lazypipe");

/**
 * Source Map 一个信息文件，里面储存着位置信息。
 * 位置信息，转换后的代码的每一个位置，所对应的转换前的位置。有了它，出错时，除错工具将直接显示原始代码，而不是转换后的代码。
 */

const stylelint = require('gulp-stylelint');

// sourcemaps 追踪报错信息
const scourcemaps = require("gulp-sourcemaps");

// 与 Bower 结合使用
const mainBowerFiles = require("main-bower-files");


const sass = require("gulp-sass");
sass.compiler = require("sass");

const imagemin = require("gulp-imagemin");
const rollup = require("gulp-better-rollup");
const babel = require("rollup-plugin-babel");
const resolve = require("rollup-plugin-node-resolve");
const commonjs = require("rollup-plugin-commonjs");
const ejs = require("rollup-plugin-ejs");

const data = require("gulp-data");
const fs = require("fs");

const isDev = process.env.NODE_ENV === "development";

/**
 * 文件路径
 * gulp 内建的 src、dest、watch 等方法，传入的文件路径的解析是通过 glob 模块处理的。
 * 匹配符
 * !：匹配非
 * *：匹配 0 个或多个字符
 * **：匹配 0 个或多个文件夹
 * {a, b, c}：匹配其中任意一项
 */
// *	匹配文件路径中的0个或多个字符，但不会匹配路径分隔符，除非路径分隔符出现在末尾
// **	匹配路径中的0个或多个目录及其子目录,需要单独出现，即它左右不能有其他东西了。如果出现在末尾，也能匹配文件。
// ?	匹配文件路径中的一个字符(不会匹配路径分隔符)
// […]	匹配方括号中出现的字符中的任意一个，当方括号中第一个字符为^或!时，则表示不匹配方括号中出现的其他字符中的任意一个，类似js正则表达式中的用法
// !(pattern|pattern|pattern)	匹配任何与括号中给定的任一模式都不匹配的
// ?(pattern|pattern|pattern)	匹配括号中给定的任一模式0次或1次，类似于js正则中的(pattern|pattern|pattern)?
// +(pattern|pattern|pattern)	匹配括号中给定的任一模式至少1次，类似于js正则中的(pattern|pattern|pattern)+
// *(pattern|pattern|pattern)	匹配括号中给定的任一模式0次或多次，类似于js正则中的(pattern|pattern|pattern)*
// @(pattern|pattern|pattern)	匹配括号中给定的任一模式1次，类似于js正则中的(pattern|pattern|pattern)
// 当有多种匹配模式时可以使用数组

// //使用数组的方式来匹配多种文件
// gulp.src(['js/*.js','css/*.css','*.html'])


// 使用数组的方式还有一个好处就是可以很方便的使用排除模式，在数组中的单个匹配模式前加上!即是排除模式，它会在匹配的结果中排除这个匹配，要注意一点的是不能在数组中的第一个元素中使用排除模式

// gulp.src([*.js,'!b*.js']) //匹配所有js文件，但排除掉以b开头的js文件
// gulp.src(['!b*.js',*.js]) //不会排除任何文件，因为排除模式不能出现在


// 数组的第一个元素中
// 此外，还可以使用展开模式。展开模式以花括号作为定界符，根据它里面的内容，会展开为多个模式，最后匹配的结果为所有展开的模式相加起来得到的结果。展开的例子如下：

// a{b,c}d 会展开为 abd,acd
// a{b,}c 会展开为 abc,ac
// a{0..3}d 会展开为 a0d,a1d,a2d,a3d
// a{b,c{d,e}f}g 会展开为 abg,acdfg,acefg
// a{b,c}d{e,f}g 会展开为 abdeg,acdeg,abdeg,abdfg

gulp.task('fonts', function () {
  return gulp.src(require('main-bower-files'))().concat('app/fonts/**/*')
    .pipe($.filter('**/*.{eot, svg, ttf, woff}'))
    .pipe($.flatten())
    .pipe(gulp.dest('dist/fonts'))
})

const htmlChannel = lazypipe()
  // .pipe(function() {
  //   return data(function (file) {
  //     　　var filePath = file.path;
  //     　　　　// global.json 全局数据，页面中直接通过属性名调用
  //     　　　　return Object.assign(JSON.parse(fs.readFileSync('/global.json')), {
  //     　　　　　　// local: 每个页面对应的数据，页面中通过 local.属性 调用
  //     　　　　　　local: JSON.parse(fs.readFileSync( path.join(path.dirname(filePath), path.basename(filePath, '.html') + '.json')))
  //     　　　　})
  //     　　})
  // })
  .pipe($.ejs)
  .pipe($.revCollectorDxb, {
    replaceReved: true,
    dirReplacements: {
      'css': '/css',
      'script': '/script',
      'cdn/': function (manifest_value) {
        return '//cdn' + (Math.floor(Math.random() * 9) + 1) + '.' + 'exsample.dot' + '/img/' + manifest_value;
      }
    }
  })
  .pipe(function () {
    return $.if(
      !isDev,
      $.htmlmin({
        removeComments: true, // 清除 HTML 注释
        collapseWhitespace: true, // 压缩空格
        collapseBooleanAttributes: true, // 省略布尔属性的值，如 <input checked="true"/> => <input checked/>
        removeEmptyAttributes: true, // 删除所有空格作属性值，如 <input id=""> => <input>
        removeScriptTypeAttributes: true, // 删除 <script> 的 type="text/javascript" 属性
        removeStyleLinkTypeAttributes: true, // 删除 <style> 和 <link> 的 type="text/css" 属性
        minifyJS: true, // 压缩页面 js
        minifyCSS: true, // 压缩页面 css
      })
    );
  });

gulp.task('html1', function () {
  const lazypipe = require('lazypipe');
  const cssChannel = lazypipe()
    .pipe($.csso)
    .pipe($.replace, 'bower_componets/bootstrap-sass-official/asstes/fonts/bootstrap', 'fonts');
  const asstes = $.useref.assets({searchPath: '{.tmp, app}'});

  return gulp.src('app/*.html')
    .pipe(asstes)
    .pipe($.if('*.js', $.uglify()))
    .pipe($.if('*.css', cssChannel))
    .pipe(asstes.restore())
    .pipe($.useref())
    .pipe($.if('*.html', $.minifyHtml({ conditionals: true, loose: true })))
    .pipe(gulp.dest('dist'))
})
gulp.task('html', function () {
  return gulp.src(['rev/**/*.json', './src/*.html', './src/views/*.html'])
    .pipe(htmlChannel())
    .pipe(gulp.dest('./dist'))

// 下面说说生成的文件路径与我们给gulp.dest()方法传入的路径参数之间的关系。
// gulp.dest(path)生成的文件路径是我们传入的path参数后面再加上gulp.src()中有通配符开始出现的那部分路径。例如：

//有通配符开始出现的那部分路径为 **/*.js
// gulp.src('script/**/*.js')
// .pipe(gulp.dest('dist')); //最后生成的文件路径为 dist/**/*.js
//如果 **/*.js 匹配到的文件为 jquery/jquery.js ,则生成的文件路径为 dist/jquery/jquery.js
// 再举更多一点的例子

// gulp.src('script/avalon/avalon.js') //没有通配符出现的情况
//     .pipe(gulp.dest('dist')); //最后生成的文件路径为 dist/avalon.js

// //有通配符开始出现的那部分路径为 **/underscore.js
// gulp.src('script/**/underscore.js')
//     //假设匹配到的文件为script/util/underscore.js
//     .pipe(gulp.dest('dist')); //则最后生成的文件路径为 dist/util/underscore.js

// gulp.src('script/*') //有通配符出现的那部分路径为 *
//     //假设匹配到的文件为script/zepto.js
//     .pipe(gulp.dest('dist')); //则最后生成的文件路径为 dist/zepto.js

// 通过指定gulp.src()方法配置参数中的base属性，我们可以更灵活的来改变gulp.dest()生成的文件路径。
// 当我们没有在gulp.src()方法中配置base属性时，base的默认值为通配符开始出现之前那部分路径，例如：

// gulp.src('app/src/**/*.css') //此时base的值为 app/src

// 上面我们说的gulp.dest()所生成的文件路径的规则，其实也可以理解成，用我们给gulp.dest()传入的路径替换掉gulp.src()中的base路径，最终得到生成文件的路径。

// gulp.src('app/src/**/*.css') //此时base的值为app/src,也就是说它的base路径为app/src
//      //设该模式匹配到了文件 app/src/css/normal.css
//     .pipe(gulp.dest('dist')) //用dist替换掉base路径，最终得到 dist/css/normal.css

// 所以改变base路径后，gulp.dest()生成的文件路径也会改变

// gulp.src(script/lib/*.js) //没有配置base参数，此时默认的base路径为script/lib
//     //假设匹配到的文件为script/lib/jquery.js
//     .pipe(gulp.dest('build')) //生成的文件路径为 build/jquery.js

// gulp.src(script/lib/*.js, {base:'script'}) //配置了base参数，此时base路径为script
//     //假设匹配到的文件为script/lib/jquery.js
//     .pipe(gulp.dest('build')) //此时生成的文件路径为 build/lib/jquery.js
    // .pipe(gulp.dest('./dist'))
    .pipe(gulp.dest(function(file) {
      return file.base;
    }))
})


// 
/**
 * 常用的 JavaScript 模块打包工具主要有 webpack、rollup 和 browserify 三个
 * Gulp 构建 ES6 开发环境通常需要借助这三者之一来合并打包 ES6 模块代码
 */

/**
 * 
 * babel 只能将 ES6 语法编译成 ES5，但不能编译 API，如 Object.assign，需引用 babel-polyfill 编译 ES6 新增 API
 * 扩展：
 * babel 只是把 ES6 模块化语法转化为 CommonJS 模块化语法，其中的 require exports 等是 CommonJS 提供的变量。
 * 任何实现 CommonJS 规范的环境（如 node 环境）可以直接运行 babel 转化的代码，而浏览器环境不支持，所以需要使用打包工具（bundler）来进行打包，就是把所有的模块组装起来，形成一个常规的 js 文件。
 * 常用的打包工具包括 browserify webpack rollup 等
 */
gulp.task('js', function () {
  return gulp.src(['./src/script/*.js', './src/*.js'])
  
    .pipe(plumber())
    .pipe($.eslint())
    .pipe($.eslint.format()) // 将 eslint 结果输出到控制台
    .pipe($.eslint.failAfterError())
    .pipe(scourcemaps.init()) // 启用 sourcemaps 功能
    .pipe(rollup(
      {
        plugins: [
          babel(),
          ejs(),
          resolve(),
          commonjs()
        ]
      },
      {
        format: "iife"
      }
    ))
    .pipe(scourcemaps.write()) // 生成记录位置信息的 sourcemaps 文件
    // .pipe($.concat('index.js'))
    .pipe($.if(!isDev, $.uglify()))
    .pipe(gulp.dest('dist/script'))
    .pipe($.revDxb())
    .pipe($.revDxb.manifest())
    .pipe(gulp.dest('./rev/js'));
gulp.task("js", function () {
  return (
    gulp
      .src(["./src/script/*.js", "./src/*.js"])
      .pipe(plumber())
      // .pipe($.eslint())
      // .pipe($.eslint.format())
      .pipe(scourcemaps.init())
      .pipe(
        rollup(
          {
            plugins: [
              babel({
                runtimeHelpers: true,
              }),
              ejs(),
              resolve(),
              commonjs(),
            ],
          },
          {
            format: "iife",
          }
        )
      )
      .pipe(scourcemaps.write())
      // .pipe($.concat('index.js'))
      .pipe($.if(!isDev, $.uglify()))
      .pipe(gulp.dest("dist/script"))
      .pipe($.revDxb())
      .pipe($.revDxb.manifest())
      .pipe(gulp.dest("./rev/js"))
  );
});

gulp.task("bower", () => {
  return (
    gulp
      // .src(mainBowerFiles(), {
      //   base: './bower_components'
      // })
      .src("./src/index.html")
      // .src(
      //   mainBowerFiles({
      //     overrides: {
      //       vue: {
      //         // 插件名称
      //         main: "dist/vue.js" // 对应的文件路径
      //       }
      //     }
      //   })
      // )
      // 输出到临时文件夹 tmp/vendors
      .pipe(gulp.dest("./tmp/vendors"))
  );
});

// 把 bower 生成的文件夹及文件合并到 dist 文件夹
// 先执行完 bower 任务再 执行 vendorJs 里的函数
gulp.task("vendorJs", ["bower"], () => {
  return gulp
    // .src("./tmp/vendors/**/*.js")
    .src([
      './tmp/vendors/**/*.js',
      // 加入要引入的 js 文件,合并来自 NPM 的 JS
      './node_modules/bootstrap/dist/js/bootstrap.bundle.min.js'
    ])
    .pipe($.concat("venders.js"))
    // gulp-order 可以通过 数组 排列自己所需要的顺序
    // 如果外部载入的插件如果需要排序
    .pipe($.order(["jquery.js", "bootstrap.js"]))
    .pipe(gulp.dest("./dist/js"));
});

gulp.task('css', function () {
  return gulp.src(['./src/styles/*.css', './src/components/**/*.css'])
  .pipe(plumber())
    // .pipe(stylelint({
    //   reporters: [
    //     {
    //       formatter: 'string',
    //       console: true
    //     }
    //   ],
    //   debug: true
    // }))
    .pipe($.postcss())
    .pipe($.base64({
      baseDir: './src/images',
      // extensions: ['svg', 'png', /\.jpg#datauri$/i],
      extensions: ['png'],
      // exclude: [/\.server\.(com|net)\/dynamic\//, '--live.jpg'],
      maxImageSize: 30 * 1024, // bytes
      debug: true
    }))
    .pipe(gulp.dest('./dist/css'))
    .pipe($.revDxb())
    .pipe($.revDxb.manifest())
    .pipe(gulp.dest('./rev/css'));
});

gulp.task("scss", function () {
  return gulp
    .src(["./src/styles/*.scss", "./src/components/**/*.scss"])
    .pipe(plumber())
    .pipe(stylelint({
      reporters: [
        {
          formatter: 'string',
          console: true
        }
      ],
      debug: true
    }))
    .pipe(scourcemaps.init())
    .pipe(sass({
      // 使用外部 sass 文件
      // 引入路径
      // // 这里的路径会直接补齐 ./node_modules/bootstrap/sass/bootstrap
// @import 'bootstrap';
      includePaths: ['./node_modules/bootstrap/sass/']
    }).on("error", sass.logError)) // 编译
    .pipe(scourcemaps.write())
    .pipe($.postcss())
    .pipe($.base64({
      baseDir: './src/images',
      // extensions: ['svg', 'png', /\.jpg#datauri$/i],
      extensions: ['png'],
      // exclude: [/\.server\.(com|net)\/dynamic\//, '--live.jpg'],
      maxImageSize: 30 * 1024, // bytes
      debug: true
    }))
    .pipe($.concat('index.css')) // 合并
    .pipe($.postcss())
    .pipe(
      $.base64({
        // baseDir 属性，如果样式表中有绝对图像路径，则此选项中指定的路径将用作基目录（相对于gulpfile）。
        baseDir: "./src",
        // extensions: ['svg', 'png', /\.jpg#datauri$/i],
        extensions: ["png"],
        // exclude: [/\.server\.(com|net)\/dynamic\//, '--live.jpg'],
        maxImageSize: 100 * 1024, // bytes
        debug: true,
      })
    )
    .pipe($.concat("index.css")) // 合并
    .pipe($.if(!isDev, $.csso()))
    .pipe(gulp.dest("./dist/css"))
    .pipe($.revDxb())
    .pipe($.revDxb.manifest())
    .pipe(gulp.dest("./rev/css"));

  // .pipe(plumber.stop())
});

// 按照顺序拼接文件
gulp.task('scripts', function() {
  return gulp.src(['./lib/file3.js', './lib/file1.js', './lib/file2.js'])
    .pipe(concat('all.js'))
    .pipe(gulp.dest('./dist/'));
});

/**
 * gulp-cache gulp-imagemin 压缩图片
 * 由于压缩图片比较耗时，所以只需要压缩修改的图片，没有修改的直接从缓存中读取。
 */
gulp.task("images", function () {
  return gulp
    // .src('app/images/**/*.+(png|jpg|jpeg|gif|svg)')
    .src("./src/images/*")
    .pipe(plumber())
    .pipe(
      $.if(
        !isDev,
        $.cache(
          imagemin([
            imagemin.optipng({
              optimizationLevel: 5, // 默认：3 取值范围：0-7（优化等级）
              progressive: true, // 默认：false 无损压缩 jpg 图片
              interlaced: true, // 默认：false 隔行扫描 gif 进行渲染
              multipass: true, // 默认：false 多次优化 svg 直到完全优化
            }),
            // imagemin.pngquant()
          ])
        )
      )
    )
    .pipe(gulp.dest("./dist/images"));
});

/**
 * 静态资源文件添加版本号，解决浏览器缓存的问题
 * 浏览器会缓存 js、css 文件，所以每次升级 js 或 css 文件后，需要浏览器重新加载这些文件也就是常说的刷新缓存。
 * 版本号添加方式
 * 1. 改变文件名称
 * 这种方式必须同时改变资源的文件名和 html 里面引用的文件名，并且一一对应。可以用 gulp-rev gulp-rev-collector 插件实现。
 * 效果：
 * {
	    "css/main.min.css" : "css/main-1d87bebe.min.css"
	    "js/main.min.js" : "js/main-61e0be79.min.js"
   }
 * 2. 在文件后缀名之后添加 ?hash
 * 这种方式只需要给 html 里面引用的文件名添加版本号即可。可以用 gulp-rev-dxb gulp-rev-collector-dxb 插件实现。
 * 效果：
 * {
	    "css/main.min.css" : "css/main.min.css?1d87bebe"
	    "js/main.min.js" : "js/main.min.js?61e0be79"
   }
 */
// gulp.task('rev', function () {
//   gulp.src(['./dist/script/**', './dist/css/**'])
//     .pipe(plumber())
//     .pipe(plugins.revDxb())
//     .pipe(plugins.revDxb.manifest())
//     .pipe(gulp.dest('./'));
// });

// // gulp.task('rev', function () {
// //   return gulp.src(['rev/**/*.json', 'src/src/*.html'])
// //     .pipe(revCollector({
// //       replaceReved: true, // 允许替换, 已经被替换过的文件
// //       dirReplcements: { // 指定目录替换集
// //         'css': 'dist/css',
// //         'js': 'dist/script'
// //       }
// //     }))
// //     .pipe(minifyHTML({
// //       empty: true,
// //       spare: true
// //     }))
// //     .pipe(gulp.dest('dist'))
// // });
// // gulp.task('rev', function () {
// //   return gulp.src(['rev/**/*.json', 'templates/**/*.html'])
// //       .pipe( revCollector({
// //           replaceReved: true,
// //           dirReplacements: {
// //               'css': '/dist/css',
// //               '/js/': '/dist/script/',
// //               'cdn/': function(manifest_value) {
// //                   return '//cdn' + (Math.floor(Math.random() * 9) + 1) + '.' + 'exsample.dot' + '/img/' + manifest_value;
// //               }
// //           }
// //       }) )
// //       .pipe( minifyHTML({
// //               empty:true,
// //               spare:true
// //           }) )
// //       .pipe( gulp.dest('dist') );
// // });



/**
 * 复制文件
 * @base 属性可以实现递归复制目录
 * 拷贝文件的时候如何保持路径
 * 1、在 gulp.src 加一个base选项，即 gulp.src(‘static/jquery/juqery.js’,{ base: ‘.’ })
 * 2、在src中使用通配符：static*\/jquery*\/jquery.js
 */


gulp.task('minFs', function() {//压缩文件
  return gulp.src('app/dist/*.html')
      .pipe(useref())//合并js
      .pipe(
          gulp('*.js',uglify())//压缩js
      )
      .pipe(gulp('*.css', minifyCSS()))//压缩css
      .pipe(RevAll.revision({//不被重命名
          dontRenameFile:[/^\/favicon.ico$/g, ".html"]
      }))
      .pipe(gulp.dest('dist'));
});

gulp.task("views", () => {
  return gulp
    .src("./assets/**/*.pug")
    .pipe($.watch("./assets/**/*.pug"))
    .pipe($.plumber())
    .pipe(
      $.pug({
        pretty: true
      })
    )
    .pipe(gulp.dest("./dist/"));
});

/**
 * watch 监控
 * gulp 4.x 版本，watch 检测一次就不再跟新了，可以使用 gulp-watch 插件
 * gulp-watch 使用说明及要求
 * 1. 监听文件变化，自动打包并且自动刷新浏览器。
 * 2. 自动打包，要求速度要快，所以
 *    1. 尽可能的将监听任务拆开
 *    2. 减少开发模式下的功耗，如不进行压缩处理，特别是图片的压缩需要时间较长。
 */
gulp.task("watch", function () {
  $.watch(
    ["./src/*.html", "./src/views/*.html", "./src/components/**/*.ejs"],
    gulp.series("html")
  ).on("change", browserSync.reload);
  $.watch(
    [
      "./src/script/*.js",
      "./src/components/**/*.js",
      "./src/components/**/*.ejs",
    ],
    gulp.series("js")
  ).on("change", browserSync.reload);
  $.watch(
    ["./src/styles/*.scss", "./src/components/**/*.scss"],
    gulp.series("scss")
  ).on("change", browserSync.reload);
});

gulp.task('watch', function () {
  $.livereload.listen(); // 启动 livereload 监听

 // 只监听 html 文件
  gulp.watch('./*.html', function (file) {
    gulp.src(file.path).pipe($.livereload)
})
})



gulp.task(
  "dev",
  gulp.series(
    'clean',
    'copy',
    gulp.parallel(
      'html',
      'js',
      'scss',
      'css',
      'images',
      'watch',
      'server',
    "clean",
    "copy",
    // gulp.parallel(
    //   // 'copy',
    //   "html",
    //   "js",
    //   "scss",
    //   "images",
    //   "watch",
    //   "server",
    //   function () {
    //     console.log("gulp start dev");
    //   },
    // ),
    )
  )
);

// gulp.task(
//   "build",
//   gulp.series(
//     "clean",
//     gulp.parallel("html", "js", "scss", "images", () => {
//       console.log("gulp start build");
//     }),
//   )
// );
